別のAWSアカウントにあるCodeCommit RepositoryをソースとするCodePipelineをCloudFormationで構築してみた
中山です
今日はタイトルの通り、別のAWSアカウントにあるCodeCommit RepositoryをソースとするCodePipelineをCloudFormationで構築してみたので、その内容をまとめてみました。
背景
まず、構築してみた経緯を少し述べます。
マルチアカウント戦略
AWSを利用する際、どのようにAWSアカウントを利用するか事前に戦略を定めて運用を開始することが多いのではないかと思います。 個人的な印象では、環境毎(Production/Staging/Develop,etc)にAWSアカウントを作成して運用するケースが多いです。 実際、AWSからもLanding ZoneというソリューションやControl Towerというサービスといった形でAWSアカウントの分割と統制の方法が述べられています。
ここ数年は多くのサービスがOrganizationsと連携したりResource Access Manager/Transit Gateway/PrivateLink/AWS SSOなどのサービスが登場したことでアカウント分割のデメリットは緩和されています。 アカウントを分割することによるデメリットであるネットワークトポロジーの複雑さ上昇や管理系サービスの設定が煩雑になることが、以前に比べると気にならなくなってきています。 もちろん、今でもそれなりに面倒ではあるのですが。
アカウント戦略の具体的な例は以下の記事によくまとまっていると思いますので、是非ご覧ください。
環境ではなくサービスに紐付くソースコードリポジトリ
あらゆるリソースを環境単位できれいに分けることができればいいのですが、それが難しいものもあります。 ソースコードリポジトリ(AWSリソースとしては、CodeCommitのGitリポジトリ)はそのひとつです。 デプロイ先の環境やデプロイのためのパイプラインは環境毎に作成すればいいですが、リポジトリを環境毎に分割するのは現実的ではありません。
そうなると、この記事のタイトルのようにCodePipelineから他のAWSアカウントにあるCodeCommitのリポジトリを参照する必要性が生じます。
(そこの貴方!「GitHubとか使えばいいやろ」とか、思いましたね?私もそう思います。)
やってみた
ということで、実際にやってみました。 なお、今回は再利用性が高そうなネタだったのでCloudFormationテンプレートを作ってみました。
参考情報
以下の記事を参考にしています。 大まかな流れはこの記事とほぼ同じです。
全体像
先に、今回構築する環境の図を示します。
構成時のポイント
今回の構成で気をつけるべきポイントを先に解説します。
Source StageでAction Roleを指定
パイプライン上のSource Stageでアクションを定義する際にRoleを指定することができます。 CodeCommitのGit Repositoryは別のアカウントに存在するため、リポジトリのあるアカウントにCodeCommitを操作できるIAM Roleを作成しておく必要があります。 また、そのRoleはCodePipelineのService RoleからAssumeRoleが可能である必要もあります。
AWS::CodePipeline::Pipeline ActionDeclaration
また、Source StageのAction Roleはパイプラインで指定するArtifact用のストレージにアクセスできる必要もあります(こちらについては次のポイントで少し補足)。 Artifact用のストレージは別のアカウントに存在するため、IAM Roleだけでなくリソースポリシー(今回の場合はBucket Policy)でもアクセスを許可する必要があります。
ArtifactLocation(S3)でKMSを利用する必要がある(CodePipelineの仕様)
CodePipelineでは、ステージ間でアーティファクトをやりとりするためにS3 Bucketを利用します。 パイプラインを作成する際、アーティファクトを暗号化するためにAWSマネージドのCMK / カスタマーマネージドのCMKのいずれかを選択する必要があります。 暗号化しないという選択肢はありません。
そこで意識する必要があることが、Source Stageで利用するRoleにCMKによる暗号化・復号の権限を付与する必要があるという点です。 RoleにもKey Policyにもアクセス許可設定を実施する必要があります。
CloudFormationテンプレートの構成
今回、CloudFormationテンプレートを3つに分けました。
- 1.事前準備
- CodePipeline Service Role
- CodeDeploy Service Role
- S3 Bucket / Bucket Policy
- Customer Master Key
- 2.Git Repository
- CodeCommit Repository
- CodePipeline (Source Stage Action Role)
- 3.CI/CD Pipeline and Environment
- EC2 Instance / Security Group
- CodeDeploy Application / Deployment Group
- CodePipeline (Pipeline)
1. 事前準備
Pipeline側のAWSアカウントにPipelineで利用するいくつかのリソースを事前に作成します。
29-33行目では、CodePipelineのService Roleに対してSource StageのAction RoleにAssumeRoleするための許可を追加しています。 なお、Action Roleの信頼ポリシーにはPipeline側のAWSアカウントを信頼する設定を別途追加します。
200-216行目では、Artifact用のS3 BucketのBucket PolicyにRepository側のAWSアカウントからアーティファクトを保存する権限を追加します。 なお、Source StageのAction Roleにもアーティファクトを保存する権限を別途追加します。
256,271行目では、アーティファクトを保存する際に暗号化するための権限をCMKのKey Policyに追加します。 なお、Source StageのAction Roleにもアーティファクトを暗号化のための権限を別途追加します。
AWSTemplateFormatVersion: 2010-09-09 Description: Step 1, Pre-requirements (in Production Account) Parameters: RepositoryAccountId: Description: Repository Account ID MaxLength: 12 MinLength: 12 Type: String Resources: PipelineRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - codepipeline.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: Pipeline PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - sts:AssumeRole Resource: - !Sub arn:aws:iam::${RepositoryAccountId}:role/* # Cross Account Access - Effect: Allow Action: - iam:PassRole Resource: - "*" Condition: StringEqualsIfExists: iam:PassedToService: - cloudformation.amazonaws.com - elasticbeanstalk.amazonaws.com - ec2.amazonaws.com - ecs-tasks.amazonaws.com - Effect: Allow Action: - codecommit:CancelUploadArchive - codecommit:GetBranch - codecommit:GetCommit - codecommit:GetUploadArchiveStatus - codecommit:UploadArchive Resource: - "*" - Effect: Allow Action: - codedeploy:CreateDeployment - codedeploy:GetApplication - codedeploy:GetApplicationRevision - codedeploy:GetDeployment - codedeploy:GetDeploymentConfig - codedeploy:RegisterApplicationRevision Resource: - "*" - Effect: Allow Action: - elasticbeanstalk:* - ec2:* - elasticloadbalancing:* - autoscaling:* - cloudwatch:* - s3:* - sns:* - cloudformation:* - rds:* - sqs:* - ecs:* Resource: - "*" - Effect: Allow Action: - lambda:InvokeFunction - lambda:ListFunctions Resource: - "*" - Effect: Allow Action: - opsworks:CreateDeployment - opsworks:DescribeApps - opsworks:DescribeCommands - opsworks:DescribeDeployments - opsworks:DescribeInstances - opsworks:DescribeStacks - opsworks:UpdateApp - opsworks:UpdateStack Resource: - "*" - Effect: Allow Action: - cloudformation:CreateStack - cloudformation:DeleteStack - cloudformation:DescribeStacks - cloudformation:UpdateStack - cloudformation:CreateChangeSet - cloudformation:DeleteChangeSet - cloudformation:DescribeChangeSet - cloudformation:ExecuteChangeSet - cloudformation:SetStackPolicy - cloudformation:ValidateTemplate Resource: - "*" - Effect: Allow Action: - codebuild:BatchGetBuilds - codebuild:StartBuild Resource: - "*" - Effect: Allow Action: - devicefarm:ListProjects - devicefarm:ListDevicePools - devicefarm:GetRun - devicefarm:GetUpload - devicefarm:CreateUpload - devicefarm:ScheduleRun Resource: - "*" - Effect: Allow Action: - servicecatalog:ListProvisioningArtifacts - servicecatalog:CreateProvisioningArtifact - servicecatalog:DescribeProvisioningArtifact - servicecatalog:DeleteProvisioningArtifact - servicecatalog:UpdateProduct Resource: - "*" - Effect: Allow Action: - cloudformation:ValidateTemplate Resource: - "*" - Effect: Allow Action: - ecr:DescribeImages Resource: - "*" DeployRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - codedeploy.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole S3Bucket: # DeletionPolicy: Retain Description: Creating Amazon S3 bucket for AWS CodePipeline artifacts Properties: BucketName: !Join - "-" - - artifacts - !Ref AWS::Region - !Ref AWS::AccountId - "pipeline" VersioningConfiguration: Status: Enabled Type: AWS::S3::Bucket S3ArtifactBucketPolicy: Type: AWS::S3::BucketPolicy Description: Setting Amazon S3 bucket policy for AWS CodePipeline access Properties: Bucket: !Ref S3Bucket PolicyDocument: Statement: - Action: - s3:PutObject Effect: Deny Principal: "*" Resource: - !Sub arn:aws:s3:::${S3Bucket}/* Condition: StringNotEquals: s3:x-amz-server-side-encryption: aws:kms - Action: - s3:* Effect: Deny Principal: "*" Resource: - !Sub arn:aws:s3:::${S3Bucket}/* Condition: Bool: aws:SecureTransport: false - Action: - s3:Get* - s3:Put* Effect: Allow Principal: AWS: - !Sub arn:aws:iam::${RepositoryAccountId}:root Resource: - !Sub arn:aws:s3:::${S3Bucket}/* - Action: - s3:ListBucket Effect: Allow Principal: AWS: - !Sub arn:aws:iam::${RepositoryAccountId}:root Resource: - !Sub arn:aws:s3:::${S3Bucket} Version: 2012-10-17 Key: Type: AWS::KMS::Key Properties: Description: An example symmetric CMK KeyPolicy: Version: 2012-10-17 Id: key-default-1 Statement: - Sid: Enable IAM User Permissions Effect: Allow Principal: AWS: - !Sub arn:aws:iam::${AWS::AccountId}:root Action: kms:* Resource: "*" - Sid: Allow administration of the key Effect: Allow Principal: AWS: - !Sub arn:aws:iam::${AWS::AccountId}:user/cm-nakayama.nobuhiro Action: - kms:Create* - kms:Describe* - kms:Enable* - kms:List* - kms:Put* - kms:Update* - kms:Revoke* - kms:Disable* - kms:Get* - kms:Delete* - kms:ScheduleKeyDeletion - kms:CancelKeyDeletion Resource: "*" - Sid: Allow use of the key Effect: Allow Principal: AWS: - !Sub arn:aws:iam::${RepositoryAccountId}:root - !GetAtt PipelineRole.Arn - !GetAtt DeployRole.Arn Action: - kms:DescribeKey - kms:Encrypt - kms:Decrypt - kms:ReEncrypt* - kms:GenerateDataKey - kms:GenerateDataKeyWithoutPlaintext Resource: "*" - Sid: Allow attachment of persistent resources Effect: Allow Principal: AWS: - !Sub arn:aws:iam::${RepositoryAccountId}:root - !GetAtt PipelineRole.Arn - !GetAtt DeployRole.Arn Action: - kms:CreateGrant - kms:ListGrants - kms:RevokeGrant Resource: "*" Condition: Bool: kms:GrantIsForAWSResource: true Alias: Type: AWS::KMS::Alias Properties: AliasName: alias/CodePipelineArtifact TargetKeyId: Ref: Key
2. Git Repository
Repository側のAWSアカウントにCodeCommit Repositoryとそれらを操作するIAM Roleを作成します。
29-31行目では、CodePipelineのService RoleによるAssumeRoleを許可する権限を追加しています。
40-45行目では、Source StageのAction Roleに対してArtifactをS3に保存するための権限を追加しています。
46-54行目では、Source StageのAction Roleに対してArtifactを暗号化して保存するための権限を追加しています。
AWSTemplateFormatVersion: 2010-09-09 Description: Step 2, CodeCommit (in Repository Account) Parameters: ProductionAccountId: Description: Production Account ID MaxLength: 12 MinLength: 12 Type: String S3BucketARN: Description: Production Account S3 Bucket ARN for Artifact (Created by 01-requirement.yml) Type: String CmkArn: Description: Production Account CMK ARN (Created by 01-requirement.yml) Type: String Resources: Repo: Type: AWS::CodeCommit::Repository Properties: RepositoryName: !Sub SampleRepository-${AWS::AccountId} RepositoryDescription: "Sample Repository" Role: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: AWS: - !Sub arn:aws:iam::${ProductionAccountId}:root Action: - sts:AssumeRole Path: / Policies: - PolicyName: source PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - s3:PutObject - s3:PutObjectAcl Resource: - !Sub ${S3BucketARN}/* - Effect: Allow Action: - kms:DescribeKey - kms:GenerateDataKey* - kms:Encrypt - kms:ReEncrypt* - kms:Decrypt Resource: - !Ref CmkArn - Effect: Allow Action: - codecommit:GetBranch - codecommit:GetCommit - codecommit:UploadArchive - codecommit:GetUploadArchiveStatus - codecommit:CancelUploadArchive Resource: - !GetAtt Repo.Arn
3. CI/CD Pipeline and Environment
Pipeline側のAWSアカウントにデプロイ先となるAWSアカウントとPipelineに関連するリソース一式(CodeDeployのリソースを含む)を作成します。
132行目では、Source StageのAction RoleとしてRepository側のAWSアカウントで作成したIAM Roleを指定しています。
AWSTemplateFormatVersion: 2010-09-09 Description: Step 3, Environment and Pipeline (in Production Account) Parameters: SubnetId: Description: Public Subnet (Internet routable) Type: String VpcId: Type: String ArtifactBucketName: Description: Production Account S3 Bucket Name for Artifact (Created by 01-requirement.yml) Type: String CmkArn: Description: Production Account CMK ARN (Created by 01-requirement.yml) Type: String CodePipelineRoleArn: Description: Production Account CodePipeline Service Role ARN (Created by 01-requirement.yml) Type: String CodeDeployRoleArn: Description: Production Account CodeDeploy Service Role ARN (Created by 01-requirement.yml) Type: String CodeCommitRoleArn: Description: Repository Account CodeCommit Action Role ARN (Created by 02-codecommit.yml) Type: String RepositoryName: Description: Repository Account CodeCommit Repository Name (Created by 02-codecommit.yml) Type: String Resources: InstanceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow http to client host VpcId: Ref: VpcId SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 Ec2Instance: Type: AWS::EC2::Instance Properties: ImageId: ami-0ee1410f0644c1cac # Tokyo Region IamInstanceProfile: !Ref InstanceProfile InstanceType: t3.micro SecurityGroupIds: - !Ref InstanceSecurityGroup SubnetId: !Ref SubnetId Tags: - Key: "Name" Value: "CodePipelineApp" UserData: !Base64 | #!/bin/bash yum -y update yum install -y ruby yum install -y aws-cli cd /home/ec2-user aws s3 cp s3://aws-codedeploy-ap-northeast-1/latest/install . --region ap-northeast-1 chmod +x ./install ./install auto InstanceProfile: Type: "AWS::IAM::InstanceProfile" Properties: Path: "/" Roles: - Ref: "Role" Role: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforAWSCodeDeploy Policies: - PolicyName: kms PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - kms:DescribeKey - kms:GenerateDataKey* - kms:Encrypt - kms:ReEncrypt* - kms:Decrypt Resource: - !Ref CmkArn CodeDeployApplication: Type: AWS::CodeDeploy::Application Properties: ComputePlatform: Server DeploymentGroup: Type: AWS::CodeDeploy::DeploymentGroup Properties: ApplicationName: !Ref CodeDeployApplication Ec2TagFilters: - Type: KEY_AND_VALUE Key: Name Value: CodePipelineApp ServiceRoleArn: !Ref CodeDeployRoleArn Pipeline: Type: AWS::CodePipeline::Pipeline Properties: RoleArn: !Ref CodePipelineRoleArn Stages: - Name: Source Actions: - Name: Source InputArtifacts: [] OutputArtifacts: - Name: SourceOutput Configuration: RepositoryName: !Ref RepositoryName BranchName: master PollForSourceChanges: false RunOrder: 1 ActionTypeId: Version: 1 Provider: CodeCommit Category: Source Owner: AWS RoleArn: !Ref CodeCommitRoleArn - Name: Release Actions: - Name: ReleaseAction InputArtifacts: - Name: SourceOutput ActionTypeId: Category: Deploy Owner: AWS Version: 1 Provider: CodeDeploy Configuration: ApplicationName: !Ref CodeDeployApplication DeploymentGroupName: !Ref DeploymentGroup RunOrder: 1 ArtifactStore: Type: S3 Location: !Ref ArtifactBucketName EncryptionKey: Id: !Ref CmkArn Type: KMS
構築手順
こんな感じのテンプレートを使って以下の手順で構築を実施します。
- テンプレート1でスタック作成
- テンプレート2でスタック作成
- サンプルアプリケーションをリポジトリにプッシュ
- テンプレート3でスタック作成
- パイプラインを実行(変更をリリース)
サンプルアプリケーションは、公式ドキュメントで紹介されているものを利用しました。
例 1: AWS CloudFormation を使用して AWS CodeCommit パイプラインを作成する
また、2つ目以降のCloudFormation Stackを作成する際に指定するパラメーターには、前の手順で作成したリソース名やARNを設定します。
動作確認
パイプラインを使ってリリースします。 こんな感じになれば無事成功です。
デプロイ先にアクセスすると、以下の画面が表示されるはずです。
まとめ
今回のポイントは、クロスアカウントであるが故にプリンシパル(IAM Roleなど、操作の主体)にもリソースポリシーにもアクセスを許可する設定を実施する必要がある点ではないかと思います。 それ以外にはいくつかの仕様をおさえておけば、そんなに難しくないかと思います。
現場からは以上です。